
/*
 * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is
 * subject to license terms.
 */ 

package com.cainli.task;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import android.content.Context;
import android.content.res.Resources;
import android.view.View;

import com.cainli.log.TLog;
import com.cainli.swingworker.SwingWorker;


/**
 * A type of {@link SwingWorker} that represents an application
 * background task.  Tasks add descriptive properties that can 
 * be shown to the user, a new set of methods for customizing
 * task completion, support for blocking input to the GUI while 
 * the Task is executing, and a {@code TaskListener} that
 * enables one to monitor the three key SwingWorker methods: {@code
 * doInBackground}, {@code process} and {@code done}.
 * 
 * <p>
 * When a Task completes, the {@code final done} method invokes 
 * one of {@code succeeded}, {@code cancelled}, {@code interrupted}, 
 * or {@code failed}.  The {@code final done} method invokes
 * {@code finished} when the completion method returns or throws
 * an exception.  
 * 
 * <p> 
 * Tasks should provide localized values for the {@code title},
 * {@code description}, and {@code message} properties in a 
 * ResourceBundle for the Task subclass.  A 
 * {@link ResourceMap} is 
 * loaded automatically using the Task subclass as the 
 * {@code startClass} and Task.class the {@code stopClass}.
 * This ResourceMap is also used to look up format strings used in
 * calls to {@link #message message}, which is used to set 
 * the {@code message} property.
 * 
 * <p>
 * For example: given a Task called {@code MyTask} defined like this:
 * <pre> 
 * class MyTask extends Task<MyResultType, Void> { 
 *     protected MyResultType doInBackground() { 
 *         message("startMessage", getPlannedSubtaskCount()); 
 *         // do the work ... if an error is encountered:
 *             message("errorMessage");
 *         message("finishedMessage", getActualSubtaskCount(), getFailureCount()); 
 *         // .. return the result
 *     } 
 * } 
 * </pre> 
 * Typically the resources for this class would be defined in the
 * MyTask ResourceBundle, @{code resources/MyTask.properties}:
 * <pre>
 * title = My Task 
 * description = A task of mine for my own purposes.   
 * startMessage = Starting: working on %s subtasks...  
 * errorMessage = An unexpected error occurred, skipping subtask
 * finishedMessage = Finished: completed %1$s subtasks, %2$s failures 
 * </pre>
 * 
 * <p>
 * Task subclasses can override resource values in their own ResourceBundles:
 * <pre>
 * class MyTaskSubclass extends MyTask {
 * }
 * # resources/MyTaskSubclass.properties
 * title = My Task Subclass 
 * description = An appropriate description
 * # ... all other resources are inherited
 * </pre>
 * 
 * <p>
 * Tasks can specify that input to the GUI is to be blocked while
 * they're being executed.  The {@code inputBlocker} property specifies
 * what part of the GUI is to be blocked and how that's accomplished.
 * The  {@code inputBlocker} is set automatically when an {@code @Action} 
 * method that returns a Task specifies a {@link BlockingScope} value
 * for the {@code block} annotation parameter.  To customize the way 
 * blocking is implemented you can define your own {@code Task.InputBlocker}.
 * For example, assume that {@code busyGlassPane} is a component
 * that consumes (and ignores) keyboard and mouse input:
 * <pre>
 * class MyInputBlocker extends InputBlocker {
 *     BusyIndicatorInputBlocker(Task task) {
 *         super(task, Task.BlockingScope.WINDOW, myGlassPane);
 *     }
 *     protected void block() {
 *         myFrame.setGlassPane(myGlassPane);
 *         busyGlassPane.setVisible(true);
 *     }
 *     protected void unblock() {
 *       busyGlassPane.setVisible(false);
 *     }
 * }
 * // ...
 * myTask.setInputBlocker(new MyInputBlocker(myTask));
 * </pre>
 * <p>
 * All of the settable properties in this class are bound, i.e. a 
 * PropertyChangeEvent is fired when the value of the property changes.
 * As with the {@code SwingWorker} superclass, all 
 * {@code PropertyChangeListeners} run on the event dispatching thread.
 * This is also true of {@code TaskListeners}.
 * 
 * <p>
 * Unless specified otherwise specified, this class is thread-safe.
 * All of the Task properties can be get/set on any thread.
 * 
 * 
 * @author Hans Muller (Hans.Muller@Sun.COM)
 * @see ApplicationContext
 * @see ResourceMap
 * @see TaskListener
 * @see TaskEvent
 */
public abstract class AbstractTask<ReturnResult, InterParam> extends SwingWorker<ReturnResult, InterParam>{
//    private static final Logger logger = Logger.getLogger(Task.class.getName());
    protected static final String TAG = "Task";
    // 上下文
    protected final Context context;
    // 资源
    private Resources resources;
    // 监听器
    private List<TaskListener<ReturnResult, InterParam>> taskListeners;
    private InputBlocker inputBlocker;  
//    private String name = null;
    private String title = null;
    private String description = null;
    private long messageTime = -1L;
    private String message = null;
    private long startTime = -1L;
    private long doneTime = -1L;
    private boolean userCanCancel = true;
    private boolean progressPropertyIsValid = false;
    private TaskService taskService = null;
    
    /**
     * 指定使用TaskService执行时候的UI组织方式.  可以通过{@link #getInputBlocker inputBlocker}来取得.
     * 
     * @see AbstractTask.InputBlocker
     * @see PlayAction#block
     */
    public enum BlockingScope {
       /** 
        * 不组织
        */
       NONE, 
       /** 
        * 组织View,通常是暂时设置view为禁用来实现
        */
       VIEW, 
       /** 
        * 阻止当前窗口，通常是通过显示模态对话框来阻止（未实现）
        */
       WINDOW, 
       /** 
        * 阻止整个应用
        */
       APPLICATION
    };

    private void initTask(Resources resources) {
        this.resources = resources;
        if (resources != null) {
            messageTime = System.currentTimeMillis();
        }
        addPropertyChangeListener(new StatePCL());
        taskListeners = new CopyOnWriteArrayList<TaskListener<ReturnResult, InterParam>>();
    }

    
    private Resources defaultResourceMap(Context context) {
        if(context != null)
        {
            return context.getResources();
        }
        else
        {
            return null;
        }
    }

    /**
     * 构造 {@code Task}. 
     * 
     * @param context 上下文
     * @param resources 资源
     * @see #getResources
     * @see #setTitle
     * @see #setDescription
     * @see #setMessage
     * @see ApplicationContext#getResourceMap
     */
    public AbstractTask(Context context, Resources resources) {
        this.context = context;
        initTask(resources);
    }

    /**
     * 构造 {@code Task}. 
     * 
     * @param context 上下文.
     * @see #getResources
     * @see #setTitle
     * @see #setDescription
     * @see #setMessage
     * @see ApplicationContext#getResourceMap
     */
    public AbstractTask(Context context) {
        this.context = context;
        initTask(defaultResourceMap(context));
    }

    public final Context getContext() {
        return context;
    }

    /**
     * 获取执行此task的TaskService，可能为null,此属性在使用taskService执行时设置，
     * 执行完毕时候清空.
     * <p>
     * 此属性为只读的
     * 
     * @return TaskService
     * @see TaskService#execute
     * @see #done
     */
    public synchronized TaskService getTaskService() {
        return taskService;
    }

    /**
     * 在使用taskService执行时设置，
     * 执行完毕时候清空.
     */
    synchronized void setTaskService(TaskService taskService) {
        TaskService oldTaskService, newTaskService;
        synchronized (this) {
            oldTaskService = this.taskService;
            this.taskService = taskService;
            newTaskService = this.taskService;
        }
        firePropertyChange("taskService", oldTaskService, newTaskService);
    }

    /**
     * 返回该Task的资源.
     * 
     * @return this Task's {@code Resources}
     * @see #resourceName
     */
    public final Resources getResources() {
        return resources;
    }

    /**
     * 获取 {@code title} 属性. 
     * <p>
     * 标题是一个简短的单行描述。默认值为空。
     * 
     * @return {@code title} 的值.
     * @see #setTitle
     * @see #setDescription
     * @see #setMessage
     */
    public synchronized String getTitle() {
        return title;
    }

    /**
     * 设置 {@code title} 属性.  
     * <p>
     * 标题是一个简短的单行描述。 {@code
     * title} 应该能具体指明这个任务是在做什么, 比如 "Loading
     * image sunset.png" 比 "Image Loader" 要好.  同样，标题不适合设置短暂的信息, 如 "Loaded 27.3%
     * of sunset.png".  使用 {@link #setMessage message} 属性来报告当前的信息
     * 
     * @param 任务的标题
     * @see #getTitle
     * @see #setDescription
     * @see #setMessage
     */
    protected void setTitle(String title) {
        String oldTitle, newTitle;
        synchronized (this) {
            oldTitle = this.title;
            this.title = title;
            newTitle = this.title;
        }
        firePropertyChange("title", oldTitle, newTitle);
    }
    
    /**
     * 获取 {@code description} 属性.
     * 默认值为null
     * <p>
     * 任务标题的较长版本.
     * 
     * @return 任务的描述
     * @see #setDescription
     * @see #setTitle
     * @see #setMessage
     */
    public synchronized String getDescription() {
        return description;
    }

    /**
     * 设置 {@code description} 属性.
     * <p> 
     * 任务标题的较长版本.
     * 
     * @param 任务的描述.
     * @see #getDescription
     * @see #setTitle
     * @see #setMessage
     */
    protected void setDescription(String description) {
        String oldDescription, newDescription;
        synchronized (this) {
            oldDescription = this.description;
            this.description = description;
            newDescription = this.description;
        }
        firePropertyChange("description", oldDescription, newDescription);
    }


    /** 
     * 返回任务的持续事件. 如果任务没执行 (如：状态为 {@code StateValue.PENDING}), 
     * 此方法返回 0.  其他情况下，返回任务的持续事件.  如, 想要知道任务执行了多少秒:
     * <pre>
     * long nSeconds = myTask.getExecutionDuration(TimeUnit.SECONDS);
     * </pre>
     * 
     * @param 返回值的时间单位
     * @return 任务执行的时间长度
     * @see #execute
     */
    public long getExecutionDuration(TimeUnit unit) {
        long startTime, doneTime, dt;
        synchronized (this) {
            startTime = this.startTime;
            doneTime = this.doneTime;
        }
        if (startTime == -1L) {
            dt = 0L;
        } else if (doneTime == -1L) {
            dt = System.currentTimeMillis() - startTime;
        } else {
            dt = doneTime - startTime;
        }
        return unit.convert(Math.max(0L, dt), TimeUnit.MILLISECONDS);
    }

    /**
     * 返回 {@code message} 属性. 
     * <p>
     * 返回一个简短的、单行消息， 用来向用户解释当前任务的适当信息。
     * 
     * @return a short one-line status message.
     * @see #setMessage
     * @see #getMessageDuration
     */
    public String getMessage() {
        return message;
    }

    /**
     * 设置 {@code message} 属性. 
     * <p>
     * 一个简短的、单行消息， 用来向用户解释当前任务的适当信息.  此消息用来反映
     * 当前任务的动态 并且可以频繁的更新.它不应该和title属性重复，也不要传达任何
     * 用户会忽略的信息
     * <p>
     * 举例来说, 一个任务的 {@code doInBackground} 方法从web service加载了一张图片。
     * 你可以在每个里程碑处设置一个新的message来提醒用户, e.g.:
     * <pre>
     * loadTask.setTitle("Loading photo from http://photos.com/sunset");
     * // ...
     * loadTask.setMessage("opening connection to photos.com");
     * // ...
     * loadTask.setMessage("reading thumbnail image file sunset.png");
     * // ... etc
     * </pre>
     * <p>
     * 每次设置该属性, {@link #getMessageDuration
     * messageDuration} 属性将被重置.
     * <p>
     * 要本地化信息的画，使用 {@link #message message} 方法.
     * 
     * @param message.
     * @see #GET_MESSAGE_ACTION
     * @see #getMessageDuration
     * @see #message
     */
    protected void setMessage(String message) {
        String oldMessage, newMessage;
        synchronized (this) {
            oldMessage = this.message;
            this.message = message;
            newMessage = this.message;
            messageTime = System.currentTimeMillis();
        }
        firePropertyChange("message", oldMessage, newMessage);
    }

    /**
     * 设置message属性并使用 {@code String.format} 来格式化.The {@code
     * resid} 用来从资源中读取消息
     * <p>
     * 如果没有设置格式化参数, 则将不格式化
     * <p> 
     * 如果没有设置Resources,则用resid作为message.
     * 
     * @param 资源ID.
     * @param 在格式化字符串中替换占位符的文本
     * @see #setMessage
     * @see ResourceMap#getString(String, Object...)
     * @see java.text.MessageFormat
     */
    protected final void message(int resid, Object... args) {
        Resources res = getResources();
        if (res != null) {
            if (args.length == 0) {
                setMessage(res.getString(resid));
            } else {
                String format = res.getString(resid);
                setMessage(String.format(format, args));
            }
        } else {
            setMessage("" + resid);
        }
    }

    /**
     * 返回从上一个设置message属性后经过的时间.
     * 
     * @param unit 返回值所使用的单位
     * @return 从上一个设置message属性后经过的时间.
     * @see #setMessage
     */
    public long getMessageDuration(TimeUnit unit) {
        long messageTime;
        synchronized (this) {
            messageTime = this.messageTime;
        }
        long dt = (messageTime == -1L) ? 0L : Math.max(0L, System
                .currentTimeMillis()
                - messageTime);
        return unit.convert(dt, TimeUnit.MILLISECONDS);
    }

    /**
     * 返回 {@code userCanCancel} 属性. 默认值为true.
     * <p>
     * 此属性通常用来决定在GUI上用户能否手动取消某个任务.
     * 
     * @return 用户能否取消某个任务.
     * @see #setUserCanCancel
     */
    public synchronized boolean getUserCanCancel() {
        return userCanCancel;
    }

    /**
     * 设置{@code userCanCancel} 属性. 默认值为true.
     * <p>
     * 此属性通常用来决定在GUI上用户能否手动取消某个任务.
     * <p>
     *
     * @param userCanCancel true 代表用户可以取消这个任务.
     * @see #getUserCanCancel
     */
    protected void setUserCanCancel(boolean userCanCancel) {
        boolean oldValue, newValue;
        synchronized (this) {
            oldValue = this.userCanCancel;
            this.userCanCancel = userCanCancel;
            newValue = this.userCanCancel;
        }
        firePropertyChange("userCanCancel", oldValue, newValue);
    }

    /**
     * 返回 {@link #setProgress progress} 的值. 用来表示任务的进度不确定，通常在GUI上表示为一个
     * 不确定的进度条
     * <p> 
     * 为了确保此值为true,任务必须保持进度始终为0.
     * 
     * @return true if the {@link #setInterParam progress} property has been set.
     * @see #setProgress
     */
    public synchronized boolean isProgressPropertyValid() {
        return progressPropertyIsValid;
    }

    /**
     * 设置进度百分比.算法:
     * <pre>
     * value - min / max - min
     * </pre>
     * 
     * @param value 当前进度 ，在min ... max区间
     * @param min 最小值
     * @param max 最大值
     * @see #setProgress(int)
     */
    protected final void setProgress(int value, int min, int max) {
        if (min >= max) {
            throw new IllegalArgumentException("invalid range: min >= max");
        }
        if ((value < min) || (value > max)) {
            throw new IllegalArgumentException("invalid value");
        }
        float percentage = (float) (value - min) / (float) (max - min);
        setProgress(Math.round(percentage * 100.0f));
    }

    /**
     * 设置进度的百分比
     * 
     * @param percentage 百分比，必须在 0.0 ... 1.0 之间
     * @see #setProgress(int)
     */
    protected final void setProgress(float percentage) {
    if ((percentage < 0.0) || (percentage > 1.0)) {
        throw new IllegalArgumentException("invalid percentage");
    }
    setProgress(Math.round(percentage * 100.0f));
    }

    /**
     * 设置进度百分比.算法:
     * <pre>
     * value - min / max - min
     * </pre>
     * 
     * @param value 当前进度 ，在min ... max区间
     * @param min 最小值
     * @param max 最大值
     * @see #setProgress(int)
     */
    protected final void setProgress(float value, float min, float max) {
        if (min >= max) {
            throw new IllegalArgumentException("invalid range: min >= max");
        }
        if ((value < min) || (value > max)) {
            throw new IllegalArgumentException("invalid value");
        }
        float percentage = (value - min) / (max - min);
        setProgress(Math.round(percentage * 100.0f));
    }

    /**
     * 相当于 {@code getState() == StateValue.PENDING}.  
     * <p> 
     * 当任务状态切换至 {@code StateValue.STARTED} ，将触发
     * 名为"started"的PropertyChangeEvent事件.  同样当
     * 任务从started状态切换到{@code StateValue.DONE}, 将触发
     * 名为"done"的PropertyChangeEvent事件
     */
    public final boolean isPending() {
        return getState() == StateValue.PENDING;
    }

    /**
     * 相当于 {@code getState() == StateValue.STARTED}.  
     * <p> 
     * 当任务状态切换至 {@code StateValue.STARTED} ，将触发
     * 名为"started"的PropertyChangeEvent事件.  同样当
     * 任务从started状态切换到{@code StateValue.DONE}, 将触发
     * 名为"done"的PropertyChangeEvent事件
     */
    public final boolean isStarted() {
        return getState() == StateValue.STARTED;
    }

    /**
     * {@inheritDoc}
     * <p>
     * 此方法触发 TaskListeners' {@link TaskListener#process process}
     * 方法. 如果你覆盖 {@code process} 并且没有调用 {@code
     * super.process(values)}, TaskListeners 将不会工作.
     * 
     * @param values @{inheritDoc}
     */
    @Override
    protected void process(List<InterParam> values) {
        fireProcessListeners(values);
    }

    @Override protected final void done() {
    try {
        if (isCancelled()) {
        cancelled();
        } 
        else {
        try {
            succeeded(get());
        } 
        catch (InterruptedException e) {
            interrupted(e);
        }
        catch (ExecutionException e) {
            failed(e.getCause());
        }
        }
    }
    finally {
        try {
        finished();
        }
        finally {
        setTaskService(null);
        }
    }
    }

    /**
     * 当调用 {@link #cancel(boolean)}方法时候被调用.
     * <p>
     * 此方法在主线程在被调用.  默认不做任何事情
     * 
     * @see #done
     */
    protected void cancelled() {
    }

    /**
     * 当任务被成功完成时调用, 如{@code get} 方法返回时. 子类应该覆盖此方法
     * <p>
     * <p>
     * 此方法在主线程在被调用.  默认不做任何事情
     *
     * @param result {@code get} 方法所返回的结果
     * @see #done
     * @see #get
     * @see #failed
     */
    protected void succeeded(ReturnResult result) {
    }

    /**
     * 在线程被打断时调用
     * <p>
     * 此方法在主线程在被调用.  默认不做任何事情
     *
     * @param e {@code get} 方法抛出的 {@code InterruptedException} 
     * @see #cancel
     * @see #done
     * @see #get
     */
    protected void interrupted(InterruptedException e) {
    }

    /**
     * 当任务执行时候失败时候调用
     * <p>
     * 此方法在主线程在被调用.  默认不做记录到日志中
     *
     * @param cause 执行失败的异常
     * @see #done
     * @see #get
     * @see #failed
     */
    protected void failed(Throwable cause) {
        String msg = String.format("%s failed: %s", this, cause);
        TLog.e(TAG, msg, cause);
    } 

    /**
     * 无条件在 以下四个方法完成后调用, {@code succeeded}, {@code failed},
     * {@code cancelled}, 或 {@code interrupted}.子类可以覆盖此方法来回收资源
     * <p>
     * 此方法在主线程在被调用.  默认不做任何事情
     *
     * @see #done
     * @see #get
     * @see #failed
     */
    protected void finished() {
    }

    /**
     * 添加TaskListener, 所有的listener方法都会在主线程中只想能够
     * 
     * @param listener 要添加的监听器
     * @see #removeTaskListener
     */
    public void addTaskListener(TaskListener<ReturnResult, InterParam> listener) {
        if (listener == null) {
            throw new IllegalArgumentException("null listener");
        }
        taskListeners.add(listener);
    }

    /**
     * 移除一个监听器
     * 
     * @param listener 要移除的监听器
     * @see #addTaskListener
     */
    public void removeTaskListener(TaskListener<ReturnResult, InterParam> listener) {
    if (listener == null) {
        throw new IllegalArgumentException("null listener");
    }
    taskListeners.remove(listener);
    }

    /**
     * 获取 Task 所有 {@code TaskListeners}的拷贝.
     * 
     * @return a copy of this Task's {@code TaskListeners}.
     * @see #addTaskListener
     * @see #removeTaskListener
     */
    public TaskListener<ReturnResult, InterParam>[] getTaskListeners() {
        return taskListeners.toArray(new TaskListener[taskListeners.size()]);
    }

    /*
     * This method is guaranteed to run on the EDT, it's called from
     * SwingWorker.process().
     */
    private void fireProcessListeners(List<InterParam> values) {
        TaskEvent<List<InterParam>> event = new TaskEvent(this, values);
        for (TaskListener<ReturnResult, InterParam> listener : taskListeners) {
            listener.process(event);
        }
    }

    /*
     * This method runs on the EDT because it's called from StatePCL (see
     * below).
     */
    private void fireDoInBackgroundListeners() {
        TaskEvent<Void> event = new TaskEvent(this, null);
        for (TaskListener<ReturnResult, InterParam> listener : taskListeners) {
            listener.doInBackground(event);
        }
    }

    /*
     * This method runs on the EDT because it's called from StatePCL (see
     * below).
     */
    private void fireSucceededListeners(ReturnResult result) {
        TaskEvent<ReturnResult> event = new TaskEvent(this, result);
        for (TaskListener<ReturnResult, InterParam> listener : taskListeners) {
            listener.succeeded(event);
        }
    }

    /*
     * This method runs on the EDT because it's called from StatePCL (see
     * below).
     */
    private void fireCancelledListeners() {
        TaskEvent<Void> event = new TaskEvent(this, null);
        for (TaskListener<ReturnResult, InterParam> listener : taskListeners) {
            listener.cancelled(event);
        }
    }

    /*
     * This method runs on the EDT because it's called from StatePCL (see
     * below).
     */
    private void fireInterruptedListeners(InterruptedException e) {
        TaskEvent<InterruptedException> event = new TaskEvent(this, e);
        for (TaskListener<ReturnResult, InterParam> listener : taskListeners) {
            listener.interrupted(event);
        }
    }

    /*
     * This method runs on the EDT because it's called from StatePCL (see
     * below).
     */
    private void fireFailedListeners(Throwable e) {
        TaskEvent<Throwable> event = new TaskEvent(this, e);
        for (TaskListener<ReturnResult, InterParam> listener : taskListeners) {
            listener.failed(event);
        }
    }

    /*
     * This method runs on the EDT because it's called from StatePCL (see
     * below).
     */
    private void fireFinishedListeners() {
        TaskEvent<Void> event = new TaskEvent(this, null);
        for (TaskListener<ReturnResult, InterParam> listener : taskListeners) {
            listener.finished(event);
        }
    }

    /*
     * This method runs on the EDT because it's called from StatePCL (see
     * below).
     */
    private void fireCompletionListeners() {
        try {
            if (isCancelled()) {
                fireCancelledListeners();
            } else {
                try {
                    fireSucceededListeners(get());
                } catch (InterruptedException e) {
                    fireInterruptedListeners(e);
                } catch (ExecutionException e) {
                    fireFailedListeners(e.getCause());
                }
            }
        } finally {
            fireFinishedListeners();
        }
    }

    private class StatePCL implements PropertyChangeListener {
        public void propertyChange(PropertyChangeEvent e) {
            String propertyName = e.getPropertyName();
            if ("state".equals(propertyName)) {
                StateValue state = (StateValue) (e.getNewValue());
                AbstractTask task = (AbstractTask) (e.getSource());
                switch (state) {
                case STARTED:
                    taskStarted(task);
                    break;
                case DONE:
                    taskDone(task);
                    break;
                }
            } else if ("progress".equals(propertyName)) {
                synchronized (AbstractTask.this) {
                    progressPropertyIsValid = true;
                }
            }
        }

        private void taskStarted(AbstractTask task) {
            synchronized (AbstractTask.this) {
                startTime = System.currentTimeMillis();
            }
            firePropertyChange("started", false, true);
            fireDoInBackgroundListeners();
        }

        private void taskDone(AbstractTask task) {
            synchronized (AbstractTask.this) {
                doneTime = System.currentTimeMillis();
            }
            try {
                task.removePropertyChangeListener(this);
                firePropertyChange("done", false, true);
                fireCompletionListeners();
            } finally {
                firePropertyChange("completed", false, true);
            }
        }
    }

    /**
     * 返回此任务的 InputBlocker.
     * <p> 
     * 
     * @see #setInputBlocker
     */
    public final InputBlocker getInputBlocker() {
        return inputBlocker;
    }

    /**
     * 设置任务的InputBlocker. 用来决定使用哪种GUI的阻挡方式
     * <p>
     * 此方法需要在
     * {@link TaskService#execute submitted}调用. 若在其后调用, 将抛出 IllegalStateException异常.
     * <p>
     * This is a bound property.
     * 
     * @see #getInputBlocker
     */
    public final void setInputBlocker(InputBlocker inputBlocker) {
        if (getTaskService() != null) {
            throw new IllegalStateException("task already being executed");
        }
        InputBlocker oldInputBlocker, newInputBlocker;
        synchronized (this) {
            oldInputBlocker = this.inputBlocker;
            this.inputBlocker = inputBlocker;
            newInputBlocker = this.inputBlocker;
        }
        firePropertyChange("inputBlocker", oldInputBlocker, newInputBlocker);
    }

    /**
     * Specifies to what extent input to the Application's GUI should
     * be blocked while this Task is being executed and provides 
     * a pair of methods, {@code block} and {@code unblock} that 
     * do the work of blocking the GUI.  For the sake of input blocking,
     * a Task begins executing when it's {@link TaskService#execute submitted}
     * to a {@code TaskService}, and it finishes executing after
     * the Task's completion methods have been called.
     * <p> 
     * The InputBlocker's {@link AbstractTask.BlockingScope
     * BlockingScope} and the blocking {@code target} object define
     * what part of the GUI's input will be blocked:
     * <dl>
     * <dt><b><code>Task.BlockingScope.NONE</code></b><dt>Don't block input.  The blocking
     * target is ignored in this case.<p></p>
     * <dt><b><code>Task.BlockingScope.ACTION</code></b><dt>Disable the target 
     * {@link javax.PlayAction.Action Action} while the Task is executing.<p></p>
     * <dt><b><code>Task.BlockingScope.COMPONENT</code></b><dt>Disable the target 
     * {@link java.awt.Component} Component while the Task is executing. <p></p>
     * <dt><b><code>Task.BlockingScope.WINDOW</code></b><dt> Block the Window ancestor of the
     * target Component while the Task is executing.<p></p>
     * <dt><b><code>Task.BlockingScope.Application</code></b><dt> Block the entire Application
     * while the Task is executing.  The blocking target is ignored
     * in this case.<p></p>
     * </dl>
     * <p>
     * Input blocking begins when the {@code block} method is called and
     * ends when {@code unblock} is called.  Each method is only
     * called once, typically by the {@code TaskService}.
     * 
     * @see AbstractTask#getInputBlocker
     * @see AbstractTask#setInputBlocker
     * @see TaskService
     * @see PlayAction
     */
    public static abstract class InputBlocker {
        private final AbstractTask task;
        private final BlockingScope scope;
        private final Object target;
//        private final ApplicationAction action;

        /**
         * Construct an InputBlocker with four immutable properties.  If
         * the Task is null or if the Task has already been 
         * executed by a TaskService, then an exception is thrown.
         * If scope is {@code BlockingScope.ACTION} then target must be 
         * a {@link javax.PlayAction.Action Action}.  If scope is 
         * {@code BlockingScope.WINDOW} or {@code BlockingScope.COMPONENT}
         * then target must be a Component.
         * 
         * @param task block input while this Task is executing
         * @param scope how much of the GUI will be blocked
         * @param target the GUI element that will be blocked
         * @param action the {@code @Action} that triggered running the task, or null
         * @see TaskService#execute
         */
        public InputBlocker(AbstractTask task, BlockingScope scope, Object target/*, ApplicationAction action*/) {
            if (task == null) {
                throw new IllegalArgumentException("null task");
            }
            if (task.getTaskService() != null) {
                throw new IllegalStateException("task already being executed");
            }
            switch (scope) {
            /*case ACTION: 
                if (!(target instanceof javax.swing.Action)) {
                    throw new IllegalArgumentException("target not an Action");
                }
                break;*/
            case VIEW:
            case WINDOW:
                if (!(target instanceof View)) {
                    throw new IllegalArgumentException("target not a Component");
                }
                break;
            }
            this.task = task;
            this.scope = scope;
            this.target = target;
           /* this.action = action;*/
        }

        /**
         * Construct an InputBlocker. If {@code target} is an 
         * {@code ApplicationAction}, it becomes the InputBlocker's
         * {@code action}.  If the Task is null or if the Task has already been 
         * executed by a TaskService, then an exception is thrown.
         * 
         * @param task block input while this Task is executing
         * @param scope how much of the GUI will be blocked
         * @param target the GUI element that will be blocked
         * @see TaskService#execute
         *//*
        public InputBlocker(Task task, BlockingScope scope, Object target) {
            this(task, scope, target, (target instanceof ApplicationAction) ? (ApplicationAction)target : null);

        }*/

        /**
         * The {@code block} method will block input while this Task
         * is being executed by a TaskService.
         * 
         * @return the value of the read-only Task property
         * @see #block
         * @see #unblock
         */
        public final AbstractTask getTask() { return task; } 

        /**
         * Defines the extent to which the GUI is blocked while 
         * the task is being executed.
         * 
         * @return the value of the read-only blockingScope  property
         * @see #block
         * @see #unblock
         */
        public final BlockingScope getScope() { return scope; }

        /**
         * Specifies the GUI element that will be blocked while 
         * the task is being executed.  
         * <p>
         * This property may be null.
         * 
         * @return the value of the read-only target  property
         * @see #getScope
         * @see #block
         * @see #unblock
         */
        public final Object getTarget() { return target; }

        /**
         * The ApplicationAction ({@code @Action}) that caused
         * the task to be executed.  The DefaultInputBlocker uses 
         * the action's {@code name} and {@code resourceMap} to 
         * configure its blocking dialog if {@code scope} is 
         * {@code BlockingScope.WINDOW}.
         * <p>
         * This property may be null.
         * 
         * @return the value of the read-only action  property
         * @see #getScope
         * @see #block
         * @see #unblock
         * @see ApplicationAction#getName
         * @see ApplicationAction#getResourceMap
         *//*
        public final ApplicationAction getAction() { return action; }*/

        /**
         * Block input to the GUI per the {@code scope} and {@code target}
         * properties.  This method will only be called once.
         * 
         * @see #unblock
         * @see TaskService#execute
         */
        protected abstract void block();

        /**
         * Unblock input to the GUI by undoing whatever the {@code block}
         * method did.  This method will only be called once.
         * 
         * @see #block
         * @see TaskService#execute
         */
        protected abstract void unblock();
    }
}
